Popular Searches
Popular Course Categories
Popular Courses

Creating Generic Classes

Creating Generic Classes

Generics and Type Safety

Creating Generic Classes in Java

Course: Core Java Training

Topic: Creating Generic Classes

Course Link: Core Java Training Course

Demo Link: Register for a Free Course Demo

Academy Website: JustAcademy


1. Introduction to Generic Classes

Generics are a feature of Java that allows classes, interfaces, and methods to work with different data types while maintaining compile-time type safety. A generic class uses one or more type parameters that are replaced with actual types when objects of the class are created.

For example, a storage class might need to store a String, Integer, Double, or custom object. Instead of creating a separate storage class for each data type, we can create one generic class and reuse it with different types.

Generic classes are commonly used in collections, reusable utility classes, data containers, repositories, and application frameworks.

Learning Objectives

  • Understand the meaning and purpose of generic classes.
  • Learn the syntax for declaring a generic class.
  • Create and instantiate generic class objects.
  • Use generic classes with different data types.
  • Work with multiple type parameters and bounded type parameters.
  • Identify common mistakes and apply best practices.

2. What Is a Generic Class?

A generic class is a class that declares one or more type parameters. These parameters act as placeholders for data types. When the class is used, actual types are supplied to determine what kinds of values it can store, accept, or return.

Consider the following declaration:

class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

In this example, T is a type parameter. It represents the type that will be selected when a Box object is created. If we create a Box<String>, its value is a String. If we create a Box<Integer>, its value is an Integer.

Important Terms

Term Meaning Example
Generic class A class that declares type parameters. class Box<T>
Type parameter A placeholder for a type. T
Type argument The actual type supplied when using the class. String in Box<String>
Parameterized type A generic type used with a particular type argument. Box<Integer>
Type safety Prevents incompatible types from being used where a specific type is expected. A String cannot be assigned to a Box<Integer>.

3. Why Do We Need Generic Classes?

Without generics, a class may use the general Object type to store different kinds of values. However, retrieving those values often requires explicit casting, and an incorrect cast may cause a runtime exception.

Example Without Generics

class Box {
    private Object value;

    public void setValue(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Box box = new Box();
        box.setValue("Hello Java");

        String result = (String) box.getValue();
        System.out.println(result);
    }
}

Output:

Hello Java

This example works, but the compiler cannot ensure that the object retrieved from the box is always a String. If another part of the program stores an Integer and the code attempts to cast it to String, a ClassCastException may occur.

Improved Example Using Generics

class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<String> box = new Box<>();
        box.setValue("Hello Java");

        String result = box.getValue();
        System.out.println(result);
    }
}

Output:

Hello Java

The generic version provides type safety and removes the need for an explicit cast when retrieving the value.

4. Syntax for Creating a Generic Class

The general syntax for a generic class is:

class ClassName<T> {
    // Fields, constructors, and methods
}

A generic class can also declare multiple type parameters:

class ClassName<T, U> {
    // Members that use T and U
}

The type parameter list appears after the class name and is enclosed in angle brackets. The parameters can be used in instance fields, constructor parameters, method parameters, and method return types.

Common Type Parameter Naming Conventions

Parameter Common Meaning Example Usage
T Type Box<T>
E Element Container<E>
K Key Pair<K, V>
V Value Pair<K, V>
N Number Calculator<N>

These letters are conventions rather than mandatory names. A descriptive type parameter name may be appropriate when it improves readability.

5. Creating and Using a Simple Generic Class

Let us create a reusable container that stores one value of a specified type.

class DataHolder<T> {
    private T data;

    public DataHolder(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

public class Main {
    public static void main(String[] args) {
        DataHolder<String> name = new DataHolder<>("Amit");
        DataHolder<Integer> age = new DataHolder<>(25);
        DataHolder<Double> price = new DataHolder<>(199.99);

        System.out.println(name.getData());
        System.out.println(age.getData());
        System.out.println(price.getData());
    }
}

Output:

Amit
25
199.99

Explanation

  • DataHolder<T> declares a generic class with one type parameter.
  • The constructor receives a value of type T.
  • The field stores the value using the selected type.
  • DataHolder<String> stores String values.
  • DataHolder<Integer> stores Integer values.
  • DataHolder<Double> stores Double values.

The same class can be reused without writing separate classes for each data type.

6. Creating Generic Class Objects

After declaring a generic class, create an object by supplying a type argument. Java can often infer the constructor's type argument from the variable declaration when the diamond operator is used.

Box<String> stringBox = new Box<String>();
Box<Integer> integerBox = new Box<Integer>();

Box<String> anotherBox = new Box<>();
Box<Integer> numberBox = new Box<>();

The last two declarations use the diamond operator, <>, which allows the compiler to infer the type arguments from the context.

Using the Objects

Box<String> textBox = new Box<>();
textBox.setValue("Generic Programming");

Box<Integer> numberBox = new Box<>();
numberBox.setValue(100);

System.out.println(textBox.getValue());
System.out.println(numberBox.getValue());

Output:

Generic Programming
100

Each object has its own type. A String value can be assigned to textBox, but an Integer cannot be assigned to it.

7. Creating a Generic Class with Multiple Type Parameters

A generic class can declare two or more type parameters when it needs to represent different types of data in one object. A common example is a pair that stores a key and a value.

class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> student =
                new Pair<>("Roll Number", 101);

        Pair<String, String> country =
                new Pair<>("India", "New Delhi");

        System.out.println(student.getKey() + ": "
                + student.getValue());

        System.out.println(country.getKey() + ": "
                + country.getValue());
    }
}

Output:

Roll Number: 101
India: New Delhi

Understanding the Example

  • K represents the key type.
  • V represents the value type.
  • The first object uses String for its key and Integer for its value.
  • The second object uses String for both its key and value.
  • The same Pair class supports both combinations while preserving type safety.

8. Creating a Generic Class with a Constructor

A generic class constructor can accept values using the class's type parameters. The constructor initializes the object's fields according to the types selected when the object is created.

class StudentRecord<T> {
    private T studentId;
    private String studentName;

    public StudentRecord(T studentId, String studentName) {
        this.studentId = studentId;
        this.studentName = studentName;
    }

    public void display() {
        System.out.println("Student ID: " + studentId);
        System.out.println("Student Name: " + studentName);
    }
}

public class Main {
    public static void main(String[] args) {
        StudentRecord<Integer> s1 =
                new StudentRecord<>(101, "Riya");

        StudentRecord<String> s2 =
                new StudentRecord<>("STU-202", "Rahul");

        s1.display();
        s2.display();
    }
}

Output:

Student ID: 101
Student Name: Riya
Student ID: STU-202
Student Name: Rahul

This design is useful when an identifier may be numeric in one system and text-based in another.

9. Bounded Type Parameters in Generic Classes

Sometimes a generic class should accept only types that satisfy a particular requirement. A bounded type parameter restricts the types that can be supplied. The extends keyword is used for an upper bound, whether the bound is a class or an interface.

For example, the following class accepts types that extend Number, such as Integer, Double, and Long.

class NumberBox<T extends Number> {
    private T number;

    public NumberBox(T number) {
        this.number = number;
    }

    public double getDoubleValue() {
        return number.doubleValue();
    }
}

public class Main {
    public static void main(String[] args) {
        NumberBox<Integer> intBox = new NumberBox<>(25);
        NumberBox<Double> doubleBox = new NumberBox<>(45.75);

        System.out.println(intBox.getDoubleValue());
        System.out.println(doubleBox.getDoubleValue());
    }
}

Output:

25.0
45.75

NumberBox<String> would not compile because String does not extend Number. The bound also allows the class to call methods defined by Number, such as doubleValue().

Multiple Bounds

A type parameter may have multiple bounds. If a class is one of the bounds, it must appear first, followed by any interface bounds.

class Processor<T extends Number & Comparable<T>> {
    private T value;

    public Processor(T value) {
        this.value = value;
    }

    public boolean isGreaterThan(T other) {
        return value.compareTo(other) > 0;
    }
}

This example requires T to be both a Number and Comparable to the same type. Integer and Double satisfy these requirements.

10. Practical Example: Generic Storage Container

Generic classes are useful for building reusable storage components. The following example creates a container that can store one value and retrieve it later.

class Storage<T> {
    private T item;

    public void store(T item) {
        this.item = item;
    }

    public T retrieve() {
        return item;
    }

    public boolean isEmpty() {
        return item == null;
    }
}

public class Main {
    public static void main(String[] args) {
        Storage<String> messageStorage = new Storage<>();
        messageStorage.store("Welcome to Core Java");

        Storage<Integer> scoreStorage = new Storage<>();
        scoreStorage.store(95);

        System.out.println(messageStorage.retrieve());
        System.out.println(scoreStorage.retrieve());
        System.out.println(messageStorage.isEmpty());
    }
}

Output:

Welcome to Core Java
95
false

This simple storage example demonstrates reuse and type safety. In production code, decide explicitly whether null should represent an empty container, or use a separate state indicator if null is also a valid stored value.

11. Generic Classes and Collections

The Java Collections Framework makes extensive use of generics. For example, ArrayList<String> stores String elements, while ArrayList<Integer> stores Integer elements.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Asha");
        names.add("Karan");

        for (String name : names) {
            System.out.println(name);
        }
    }
}

Output:

Asha
Karan

Collections illustrate the value of generic programming: the same collection implementation can safely handle different element types through different parameterizations.

12. Important Rules and Restrictions

  • Primitive types cannot be type arguments: use Integer rather than int, and Double rather than double.
  • Type parameters are not ordinary class names: T is a placeholder resolved through the generic type declaration.
  • Direct instantiation of a type parameter is not allowed: new T() is invalid because the compiler does not know which constructor should be invoked.
  • Static fields cannot use a class's type parameter as their own type: a static field belongs to the class, not to an individual parameterized instance.
  • Parameterized types are invariant: Box<Integer> is not a subtype of Box<Number>, even though Integer is a subtype of Number.
  • Generics use type erasure: the compiler uses type information for checking and then erases type parameters from much of the generated bytecode.
  • Prefer parameterized types over raw types: using Box instead of Box<String> loses compile-time type checking and may produce warnings.

13. Common Mistakes When Creating Generic Classes

Mistake Why It Is a Problem Correct Approach
Using int as a type argument Primitive types cannot be generic type arguments. Use Integer.
Forgetting the type parameter declaration The compiler does not recognize T as a type variable unless it is declared. Declare class Box<T>.
Passing an incompatible value It violates the selected type and causes a compile-time error. Pass values compatible with the type argument.
Using raw types unnecessarily Type safety is reduced and unchecked warnings may occur. Use parameterized types such as Box<String>.
Attempting new T() The runtime type's constructor is not available through T alone. Pass an object to the constructor or use an appropriate factory strategy.
Assuming generic types are covariant Box<Integer> cannot be assigned to Box<Number>. Use an appropriate wildcard when a flexible reference is needed.

14. Best Practices for Generic Class Design

  • Use meaningful class names such as DataHolder<T>, Pair<K, V>, or Repository<T>.
  • Use conventional type parameter names where they make the code easier to understand.
  • Specify type arguments at variable declarations to make intended types clear.
  • Use the diamond operator when the compiler can infer the type arguments.
  • Use bounded type parameters when the class needs methods provided by a particular parent type or interface.
  • Avoid raw types and unchecked casts unless interoperability with legacy APIs requires them.
  • Keep generic classes focused on one responsibility and document any constraints on their type parameters.
  • Remember that generic type arguments do not accept primitives; Java's wrapper types and autoboxing make common use convenient.

15. Practice Exercises

Exercise 1: Generic Message Class

Create a class named Message<T> with a field, constructor, and method to display a message value. Create objects for String and Integer values.

Exercise 2: Generic Pair

Create a generic class named Pair<K, V> that stores a product name and its price. Print the name and price using getter methods.

Exercise 3: Bounded Generic Class

Create a class named NumericValue<T extends Number> that stores a number and returns its double value. Test it with Integer and Double objects.

Exercise 4: Generic Collection Wrapper

Create a class named ListContainer<T> that internally stores a list of values. Add methods to insert an item, retrieve the list, and display all items.

16. Mini Project: Reusable Product Information Holder

Objective: Create a reusable generic class that stores a product identifier of different possible types and a product name.

class ProductInfo<ID> {
    private ID productId;
    private String productName;

    public ProductInfo(ID productId, String productName) {
        this.productId = productId;
        this.productName = productName;
    }

    public void displayInfo() {
        System.out.println("Product ID: " + productId);
        System.out.println("Product Name: " + productName);
    }
}

public class Main {
    public static void main(String[] args) {
        ProductInfo<Integer> p1 =
                new ProductInfo<>(501, "Keyboard");

        ProductInfo<String> p2 =
                new ProductInfo<>("PRD-502", "Mouse");

        p1.displayInfo();
        p2.displayInfo();
    }
}

Expected Output:

Product ID: 501
Product Name: Keyboard
Product ID: PRD-502
Product Name: Mouse

Project extensions:

  • Add getter methods for the identifier and product name.
  • Create additional products with String and Integer identifiers.
  • Store multiple ProductInfo objects in a list, using one consistent identifier type per list.
  • Add validation to reject blank product names.

17. Interview Questions and Answers

Q1. What is a generic class in Java?

A generic class declares one or more type parameters so that it can work with different data types while maintaining compile-time type safety.

Q2. What is the purpose of the T parameter?

T is a conventional placeholder for a type. When an object is created, a type argument such as String or Integer specifies the type represented by T.

Q3. What are the main advantages of generic classes?

They provide type safety, reduce explicit casting, improve code reuse, and make APIs easier to understand.

Q4. Can a generic class have more than one type parameter?

Yes. For example, class Pair<K, V> declares two parameters that can represent different types.

Q5. What is a bounded type parameter?

It restricts the allowed type arguments. For example, T extends Number accepts Number and its subclasses.

Q6. Can primitive types be used as generic type arguments?

No. Wrapper classes such as Integer, Double, and Boolean must be used instead of int, double, and boolean.

Q7. What is the diamond operator?

The diamond operator, <>, allows Java to infer generic type arguments from the context when creating an object.

Q8. Can we write new T() inside a generic class?

No. The type parameter does not provide enough information to instantiate an object directly. A constructor argument, factory, or other suitable creation mechanism is needed.

Q9. What is the difference between a type parameter and a type argument?

A type parameter is the placeholder declared in the class, such as T in Box<T>. A type argument is the actual type supplied, such as String in Box<String>.

Q10. What is type erasure?

Type erasure is the process by which the Java compiler removes or replaces generic type information in the generated bytecode while inserting necessary casts and preserving type safety where possible.

18. Quick Revision Checklist

  • Understand what generic classes are and why they are useful.
  • Know the syntax class ClassName<T>.
  • Know how to create objects using types such as Box<String>.
  • Understand constructors, fields, and methods that use type parameters.
  • Practice declaring multiple type parameters such as <K, V>.
  • Understand bounds such as <T extends Number>.
  • Remember the restrictions on primitive types, static members, and direct type-parameter instantiation.
  • Use generics to improve reuse and compile-time type safety.

19. Conclusion

Creating generic classes is an important Core Java skill that helps developers build reusable, flexible, and type-safe components. By declaring type parameters, you can design a single class that works with multiple reference types without relying on unsafe casts. Understanding constructors, multiple type parameters, bounded types, and generic collections provides a strong foundation for writing maintainable Java applications.

To continue learning, explore the Core Java Training Course, visit the course demo registration page, or browse the JustAcademy website.

Previous Next
whatsapp